using System;
using System.IO;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public struct DBTableIndex
{
    public string tblName;
    public int dataLength;
    public int dataOffset;
}

public class CTableCache<T> where T : BinaryHelper.ISerializable, new()
{
    private static readonly string TableName =
        (string)typeof(T).GetMethod("GetTableName").Invoke(null, null);
    private static readonly string TableKeyName =
        (string)typeof(T).GetMethod("GetTableKeyName").Invoke(null, null);
    private static readonly MethodInfo GetTableKeyValue =
        typeof(T).GetMethod("GetTableKeyValue");

    private List<T> m_table;
    private Dictionary<UInt32, T> m_mapTable;

    public List<T> GetListTable()
    {
        return m_table;
    }
    public Dictionary<UInt32, T> GetTable()
    {
        return m_mapTable;
    }

    public T GetListEntry(int index)
    {
        return m_table.Count > index ? m_table[index] : default;
    }
    public T GetEntry(UInt32 key)
    {
        return m_mapTable.TryGetValue(key, out T value) ? value : default;
    }

    public bool LoadData(Dictionary<string, DBTableIndex> tblIdxs, byte[] data, int offset)
    {
        if (!tblIdxs.TryGetValue(TableName, out DBTableIndex tblIdx))
        {
            return false;
        }
        var hasKey = TableKeyName.Length != 0;
        if (hasKey)
        {
            m_mapTable = new Dictionary<UInt32, T>();
        }
        else
        {
            m_table = new List<T>();
        }
        var buffer = new NetBuffer(data, offset + tblIdx.dataOffset,
            offset + tblIdx.dataOffset + tblIdx.dataLength);
        while (!buffer.IsReadableEmpty())
        {
            var entity = new T();
            entity.LoadFromBuffer(buffer);
            if (hasKey)
            {
                var key = GetTableKeyValue.Invoke(entity, null);
                m_mapTable.Add((uint)key, entity);
            }
            else
            {
                m_table.Add(entity);
            }
        }
        return true;
    }
}

public class DBMgr
{
    public static DBMgr Instance { get; } = new DBMgr();

    public CTableCache<string_text_list> tblStringTextList;
    public CTableCache<Configure> tblConfigure;
    public CTableCache<MapInfo> tblMapInfo;
    public CTableCache<MapZone> tblMapZone;
    public CTableCache<MapGraveyard> tblMapGraveyard;
    public CTableCache<TeleportPoint> tblTeleportPoint;
    public CTableCache<LandmarkPoint> tblLandmarkPoint;
    public CTableCache<ItemPrototype> tblItemPrototype;
    public CTableCache<ItemEquipPrototype> tblItemEquipPrototype;
    public CTableCache<CharPrototype> tblCharPrototype;
    public CTableCache<SObjPrototype> tblSObjPrototype;
    public CTableCache<LootSet> tblLootSet;
    public CTableCache<LootSetGroup> tblLootSetGroup;
    public CTableCache<LootSetGroupItem> tblLootSetGroupItem;
    public CTableCache<LootSetGroupCheque> tblLootSetGroupCheque;
    public CTableCache<SpellInfo> tblSpellInfo;
    public CTableCache<SpellLevelInfo> tblSpellLevelInfo;
    public CTableCache<SpellLevelEffectInfo> tblSpellLevelEffectInfo;
    public CTableCache<QuestPrototype> tblQuestPrototype;
    public CTableCache<PlayerBase> tblPlayerBase;
    public CTableCache<PlayerAttribute> tblPlayerAttribute;
    public CTableCache<CreatureAttribute> tblCreatureAttribute;
    public CTableCache<CreatureCustomAttribute> tblCreatureCustomAttribute;
    public CTableCache<ShopPrototype> tblShopPrototype;

    private Dictionary<string, DBTableIndex> m_tblIdxs;
    private byte[] m_dataBinary;
    private int m_dataOffset;

    private DBMgr()
    {
        GenericBinaryHelper.DoInit();
    }

    public string GetText(uint id, STRING_TEXT_TYPE type)
    {
        var textId = ((uint)type << 24) + id;
        var entry = tblStringTextList.GetEntry(textId);
        return entry != null ? entry.stringCN : string.Empty;
    }

    public IEnumerator AsyncLoadDB()
    {
        var url = Application.streamingAssetsPath + "/worlddb.bin";
        using (var webRequest = UnityWebRequest.Get(url))
        {
            yield return webRequest.SendWebRequest();
            if (webRequest.isNetworkError || webRequest.isHttpError)
            {
                Debug.Log(webRequest.error);
                yield break;
            }
            m_dataBinary = webRequest.downloadHandler.data;
            var stream = new MemoryStream(
                m_dataBinary, 0, m_dataBinary.Length, false, true);
            m_tblIdxs = ReadAllDBTableIndex(stream);
            m_dataOffset = (int)stream.Position;
            LoadAllDBTable();
            m_dataBinary = null;
            m_tblIdxs = null;
        }
    }

    private void LoadAllDBTable()
    {
        LoadDBTable(ref tblStringTextList);
        LoadDBTable(ref tblConfigure);
        LoadDBTable(ref tblMapInfo);
        LoadDBTable(ref tblMapZone);
        LoadDBTable(ref tblMapGraveyard);
        LoadDBTable(ref tblTeleportPoint);
        LoadDBTable(ref tblLandmarkPoint);
        LoadDBTable(ref tblItemPrototype);
        LoadDBTable(ref tblItemEquipPrototype);
        LoadDBTable(ref tblCharPrototype);
        LoadDBTable(ref tblSObjPrototype);
        LoadDBTable(ref tblLootSet);
        LoadDBTable(ref tblLootSetGroup);
        LoadDBTable(ref tblLootSetGroupItem);
        LoadDBTable(ref tblLootSetGroupCheque);
        LoadDBTable(ref tblSpellInfo);
        LoadDBTable(ref tblSpellLevelInfo);
        LoadDBTable(ref tblSpellLevelEffectInfo);
        LoadDBTable(ref tblQuestPrototype);
        LoadDBTable(ref tblPlayerBase);
        LoadDBTable(ref tblPlayerAttribute);
        LoadDBTable(ref tblCreatureAttribute);
        LoadDBTable(ref tblCreatureCustomAttribute);
        LoadDBTable(ref tblShopPrototype);
    }

    private bool LoadDBTable<T>(ref CTableCache<T> tblVal) where T : BinaryHelper.ISerializable, new()
    {
        tblVal = new CTableCache<T>();
        return tblVal.LoadData(m_tblIdxs, m_dataBinary, m_dataOffset);
    }

    private Dictionary<string, DBTableIndex> ReadAllDBTableIndex(MemoryStream stream)
    {
        var tblIdxs = new Dictionary<string, DBTableIndex>();
        var tblIdx = new DBTableIndex();
        int offset = 0;
        while (ReadDBTableIndex(stream, ref tblIdx, ref offset))
        {
            tblIdxs[tblIdx.tblName] = tblIdx;
        }
        return tblIdxs;
    }

    private bool ReadDBTableIndex(MemoryStream stream, ref DBTableIndex tblIdx, ref int offset)
    {
        int n = stream.ReadByte();
        if (n <= 0)
        {
            return false;
        }
        tblIdx.tblName = System.Text.Encoding.UTF8.GetString(
            stream.GetBuffer(), (int)stream.Position, n);
        stream.Position += n;
        var buffer = new NetBuffer(
            stream.GetBuffer(), (int)stream.Position, (int)stream.Length);
        tblIdx.dataLength = (int)Utils.ReadFromBuffer7BitEncodedInt(buffer);
        stream.Position = buffer.GetPosition();
        tblIdx.dataOffset = offset;
        offset += tblIdx.dataLength;
        return true;
    }
}